package com.bestcem.xm.survey.controller;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.net.URLEncodeUtil;
import cn.hutool.core.util.*;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.bestcem.bp.xm.user.grpc.v1.services.Organization;
import com.bestcem.xm.common.core.domain.web.XmResultJson;
import com.bestcem.xm.common.core.enums.ReqErrorCodeEnum;
import com.bestcem.xm.common.core.exception.BusinessException;
import com.bestcem.xm.common.core.uitls.SignUtil;
import com.bestcem.xm.component.security.annotation.AuthIgnore;
import com.bestcem.xm.componet.filestorage.service.StorageService;
import com.bestcem.xm.componet.redis.service.RedisService;
import com.bestcem.xm.openplatform.grpc.v1.services.EncryptionConfig;
import com.bestcem.xm.survey.config.SurveyWechatSetting;
import com.bestcem.xm.survey.controller.vo.SurveyQuotaCheckItemVo;
import com.bestcem.xm.survey.convert.grpc.SurveyGrpcConvertMapper;
import com.bestcem.xm.survey.entity.SurveyQuotaCheckItem;
import com.bestcem.xm.survey.entity.SurveyQuotaCondition;
import com.bestcem.xm.survey.entity.SurveyRespondent;
import com.bestcem.xm.survey.entity.SurveyRespondentQuotaCheckItem;
import com.bestcem.xm.survey.enums.RespondentSourceEnum;
import com.bestcem.xm.survey.enums.SurveyErrorCodeEnum;
import com.bestcem.xm.survey.grpc.client.OpenPlatformGrpcClient;
import com.bestcem.xm.survey.grpc.client.QdesGrpcClient;
import com.bestcem.xm.survey.grpc.client.UserGrpcClient;
import com.bestcem.xm.survey.grpc.client.dto.OrgInfoDto;
import com.bestcem.xm.survey.grpc.client.dto.ProjectDto;
import com.bestcem.xm.survey.mq.dto.CreateQuotaDto;
import com.bestcem.xm.survey.mq.send.biz.SurveyMessageSendService;
import com.bestcem.xm.survey.service.*;
import com.bestcem.xm.survey.util.SurveyConstant;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import io.swagger.annotations.ApiOperation;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.bson.types.ObjectId;
import org.javatuples.Pair;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

import static com.bestcem.xm.deliver.grpc.v1.services.DeliverExtparamsExtparamType.*;
import static com.bestcem.xm.deliver.grpc.v1.services.DeliverExtparamsStatus.DELIVEREXTPARAMS_STATUS_START_VALUE;
import static com.bestcem.xm.openplatform.grpc.v1.services.EncryptionConfigEType.ENCRYPTIONCONFIG_E_TYPE_AES_VALUE;
import static com.bestcem.xm.openplatform.grpc.v1.services.ExtParamEnumManageStatus.EXTPARAMENUMMANAGE_STATUS_START_VALUE;
import static com.bestcem.xm.survey.enums.RespondentSourceEnum.RESPONDENT_SOURCE_PREVIEW;
import static com.bestcem.xm.survey.enums.RespondentStatusEnum.RESPONDENT_STATUS_BEGIN;
import static com.bestcem.xm.survey.enums.RespondentStatusEnum.RESPONDENT_STATUS_DELETE;
import static com.bestcem.xm.survey.enums.RespondentStatusEnum.RESPONDENT_STATUS_DISCARD;
import static com.bestcem.xm.survey.enums.WxInfoCollectionEnum.WX_INFO_COLLECTION_SILENT;
import static com.bestcem.xm.survey.grpc.v1.services.RespondentDataStatus.RESPONDENT_DATA_STATUS_FORMAL_VALUE;
import static com.bestcem.xm.survey.grpc.v1.services.RespondentDataStatus.RESPONDENT_DATA_STATUS_TEST_VALUE;
import static com.bestcem.xm.survey.grpc.v1.services.RespondentSource.RESPONDENT_SOURCE_PREVIEW_VALUE;
import static com.bestcem.xm.survey.grpc.v1.services.RespondentSource.RESPONDENT_SOURCE_WECHATMP_VALUE;
import static com.bestcem.xm.survey.grpc.v1.services.RespondentStatus.*;
import static com.bestcem.xm.survey.util.SurveyConstant.*;

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/survey")
@AuthIgnore
public class SurveyHandlerController {

    private @NonNull QdesGrpcClient qdesGrpcClient;
    private @NonNull UserGrpcClient userGrpcClient;
    private @NonNull OpenPlatformGrpcClient openPlatformGrpcClient;

    private @NonNull SurveyGrpcConvertMapper surveyGrpcConvertMapper;

    //private @NonNull OssService ossService;
    private @NonNull SurveyCommonService surveyCommonService;
    private @NonNull JwtTokenService jwtTokenService;
    private @NonNull SurveyRespondentService surveyRespondentService;
    private @NonNull RedisService redisService;
    private @NonNull SurveyQuotaConditionService surveyQuotaConditionService;
    private @NonNull SurveyRespondentQuotaCheckItemService surveyRespondentQuotaCheckItemService;
    private @NonNull SurveyMessageSendService surveyMessageSendService;
    private @NonNull SurveyRespondentAttachmentService surveyRespondentAttachmentService;
    private @NonNull SurveyRespondentPropDataService surveyRespondentPropDataService;

    private @NonNull StorageService storageService;

    private @NonNull SurveyWechatSetting surveyWechatSetting;

    private @NonNull ObjectMapper objectMapper;

    @ApiOperation(value = "答题预览")
    @GetMapping("/projects/{id:[a-fA-F0-9]{24}}")
    public JSONObject getProject(@PathVariable String id, @RequestParam("source") Integer source,
                                 @RequestParam(name = "version", required = false) Integer version,
                                 HttpServletRequest request) throws JsonProcessingException {
        boolean is_test_mode;
        com.bestcem.xm.qdes.grpc.v1.services.QdesProject qdesProject;
        if (source.equals(RESPONDENT_SOURCE_PREVIEW.getValue())) {
            if (ObjectUtils.isEmpty(version)) {
                qdesProject = qdesGrpcClient.getCurrentProject(id);
            } else {
                qdesProject = qdesGrpcClient.getReleasedProject(id, version);
            }
            is_test_mode = true;
        } else {
            qdesProject = qdesGrpcClient.getCurrentProject(id);
            is_test_mode = false;
        }
        ProjectDto projectWithQstruct = surveyGrpcConvertMapper.toProjectDto(qdesProject);

        com.bestcem.xm.qdes.grpc.v1.services.Project grpcProject = qdesGrpcClient.getProject(id);
        ProjectDto projectAlone = surveyGrpcConvertMapper.toProjectDto(grpcProject);

        //ObjectMapper objectMapper = new ObjectMapper();
        //objectMapper.registerModule(new JavaTimeModule());

        String json1 = objectMapper.writeValueAsString(projectWithQstruct);
        //带qstruct的jsonobject
        JSONObject proj = JSONObject.parseObject(json1);

        String json2 = objectMapper.writeValueAsString(projectAlone);
        //不带qstruct的jsonobject
        JSONObject proj_attr = JSONObject.parseObject(json2).getJSONObject("customAttr");

        if (!is_test_mode && !ObjectUtils.isEmpty(proj_attr)
                && proj_attr.getBooleanValue("ipOnce")
                && surveyCommonService.checkIpExisted(ServletUtil.getClientIP(request), proj)) {
            log.error("答题预览[getProject],您所在的IP已经参与过该调查");
            throw new BusinessException(SurveyErrorCodeEnum.IP_CHECK_ERROR.getValue(), "您所在的IP已经参与过该调查");
        }

        JSONObject qstruct = proj.getJSONObject("qstruct");
        JSONObject data = qstruct.getJSONObject("data");
        if (!data.isEmpty()) {
            Object newItems = surveyCommonService.removeSeq(data.get("items"));
            data.put("items", newItems);
        }

        JSONObject ret = new JSONObject();
        ret.put("version", proj.getInteger("version"));
        ret.put("status", proj.getInteger("status"));
        ret.put("project", surveyCommonService.parseProject(data, proj_attr));
        ret.put("templates", qstruct.getOrDefault("templates", new JSONArray()));
        ret.put("policy", qstruct.getOrDefault("policy", new JSONArray()));
        ret.put("attachments", qstruct.getOrDefault("attachments", new JSONArray()));
        ret.put("gcss", qstruct.getOrDefault("gcss", new JSONObject()));
        ret.put("customAttr", proj_attr);
        return ret;
    }

    @ApiOperation(value = "答题预览post")
    @PostMapping("/{projectId:[a-fA-F0-9]{24}}")
    public XmResultJson<?> postSurveyHandler(@PathVariable(name = "projectId") String project_id,
                                             @RequestParam(name = "source", defaultValue = "0") Integer source,
                                             @RequestBody JSONObject param_dict,
                                             HttpServletRequest request,
                                             HttpServletResponse response) {
        //这种不行，param_dict后面改了，params也会变
        //JSONObject params = new JSONObject(param_dict);
        JSONObject params = new JSONObject();
        params.putAll(param_dict);

        source = getSource(source, param_dict);

        JSONObject ext_params = new JSONObject();
        JSONObject ext_params_code_map = new JSONObject();

        //String project_js_uri = ossService.getCdn() + ossService.getFilePrefix() + SurveyConstant.getSurveyProjectJsPathV2(project_id);
        //String project_js_uri =  storageService.getByCondition(null,String.format("survey/project/%s/", project_id),"project.js").getUrl();
        String project_js_uri = storageService.getPropertiesValueBy(null, "domain") + storageService.getPropertiesValueBy(null, "base-path") + SurveyConstant.getSurveyProjectJsPathV2(project_id);
        int footer;
        boolean xm_custom = param_dict.getBooleanValue("xm_custom");
        //验证是否是定制链接
        if (xm_custom) {
            Long xm_link_aging_time = param_dict.getLong("xm_link_aging_time");
            //判断问卷链接有效时长
            if (Objects.nonNull(xm_link_aging_time) && (System.currentTimeMillis() > xm_link_aging_time)) {
                Map<String, Object> map = new HashMap<>();
                map.put("project_js", project_js_uri);
                return XmResultJson.fail(map, Integer.valueOf(SurveyErrorCodeEnum.LINK_DISABLE.getValue()), "链接已失效");
            }

            //预览的情况下问卷外部参数自己拼接，前端自己有，不用返回
            if (!source.equals(RESPONDENT_SOURCE_PREVIEW.getValue())) {
                //解析外部参数的值
                String deliver_id = param_dict.getString("deliver_id");
                Organization org = userGrpcClient.getByIdOrCode(null, param_dict.getString("org_code"));
                if (Objects.isNull(org)) {
                    return XmResultJson.fail(-1, "机构不存在！");
                }
                String org_id = org.getId();
                log.info("postSurveyHandler org_id={}", org_id);
                JSONObject enum_params = surveyCommonService.getEnumExtParams(org_id, EXTPARAMENUMMANAGE_STATUS_START_VALUE);
                log.info("postSurveyHandler enum_params={}", enum_params.toJSONString());
                //当前投放的所有外部参数
                List<JSONObject> deliver_extparams = surveyCommonService.getDeliverExtParams(deliver_id, project_id);
                String aes_key = null;
                for (JSONObject ext_param : deliver_extparams) {
                    if (StrUtil.equals("", param_dict.getString(ext_param.getString("extparam_name")))) {
                        ext_params.put(ext_param.getString("extparam_name"), "");
                    } else {
                        if (StrUtil.equals("1", ext_param.getString("aes_encrypt"))) {

                            //取aes_key
                            if (StrUtil.isEmptyIfStr(aes_key)) {
                                JSONObject queryMap = new JSONObject();
                                queryMap.put("org_id", ext_param.getString("org_id"));
                                queryMap.put("e_type", ENCRYPTIONCONFIG_E_TYPE_AES_VALUE);
                                List<EncryptionConfig> encryptconfig_list = openPlatformGrpcClient.listEncryptionConfig(queryMap);
                                String aes_config_str = encryptconfig_list.stream().findFirst().map(EncryptionConfig::getAesConfig).orElseThrow(() -> new BusinessException("aes config error"));
                                aes_key = Optional.ofNullable(JSONObject.parseObject(aes_config_str).getString("aes_key")).orElse("");
                            }

                            String ext_param_value = SignUtil.decryptFromString(aes_key, param_dict.getString(ext_param.getString("extparam_name")), Mode.ECB, Padding.ZeroPadding);
                            ext_params.put(ext_param.getString("extparam_name"), surveyCommonService.extparamsTransfer(ext_param, ext_param_value));

                            if (ext_param.getInteger("extparam_type").equals(DELIVEREXTPARAMS_EXTPARAM_TYPE_DATA_ENUM_VALUE)) {
                                ext_params_code_map.putAll(
                                        surveyCommonService.formationExtparams(
                                                ext_param.getString("extparam_name"),
                                                Optional.ofNullable(enum_params.getJSONObject(ext_param.getString("extparam_name"))).map(its -> its.getString(ext_param_value)).orElse(""),
                                                DELIVEREXTPARAMS_EXTPARAM_TYPE_DATA_ENUM_VALUE,
                                                ext_param_value
                                        )
                                );
                            } else if (ext_param.getInteger("extparam_type").equals(DELIVEREXTPARAMS_EXTPARAM_TYPE_DATA_BOOL_VALUE)
                                    || ext_param.getInteger("extparam_type").equals(DELIVEREXTPARAMS_EXTPARAM_TYPE_DATA_TIMESTRAMP_VALUE)) {
                                ext_params_code_map.putAll(
                                        surveyCommonService.formationExtparams(
                                                ext_param.getString("extparam_name"),
                                                surveyCommonService.extparamsTransfer(ext_param, ext_param_value),
                                                ext_param.getInteger("extparam_type"),
                                                null
                                        )
                                );
                            }
                        } else {
                            ext_params.put(ext_param.getString("extparam_name"), surveyCommonService.extparamsTransfer(ext_param, param_dict.getString(ext_param.getString("extparam_name"))));

                            if (ext_param.getInteger("extparam_type").equals(DELIVEREXTPARAMS_EXTPARAM_TYPE_DATA_ENUM_VALUE)) {
                                ext_params_code_map.putAll(
                                        surveyCommonService.formationExtparams(
                                                ext_param.getString("extparam_name"),
                                                Optional.ofNullable(enum_params.getJSONObject(ext_param.getString("extparam_name"))).map(its -> its.getString(param_dict.getString(ext_param.getString("extparam_name")))).orElse(""),
                                                DELIVEREXTPARAMS_EXTPARAM_TYPE_DATA_ENUM_VALUE,
                                                param_dict.getString(ext_param.getString("extparam_name"))
                                        )
                                );
                            } else if (ext_param.getInteger("extparam_type").equals(DELIVEREXTPARAMS_EXTPARAM_TYPE_DATA_BOOL_VALUE)
                                    || ext_param.getInteger("extparam_type").equals(DELIVEREXTPARAMS_EXTPARAM_TYPE_DATA_TIMESTRAMP_VALUE)) {
                                ext_params_code_map.putAll(
                                        surveyCommonService.formationExtparams(
                                                ext_param.getString("extparam_name"),
                                                surveyCommonService.extparamsTransfer(ext_param, param_dict.getString(ext_param.getString("extparam_name"))),
                                                ext_param.getInteger("extparam_type"),
                                                null
                                        )
                                );
                            }
                        }
                    }
                }
            } else {
                com.bestcem.xm.qdes.grpc.v1.services.Project project = qdesGrpcClient.getProject(project_id);
                String company_id = project.getCompanyId();

                JSONObject enum_params = surveyCommonService.getEnumExtParams(company_id, EXTPARAMENUMMANAGE_STATUS_START_VALUE);

                JSONObject query = new JSONObject();
                query.put("org_id", company_id);
                query.put("status", Lists.newArrayList(DELIVEREXTPARAMS_STATUS_START_VALUE));
                List<JSONObject> org_ext_param = surveyCommonService.listExtparam(query);

                if (!CollectionUtils.isEmpty(org_ext_param)) {
                    for (JSONObject ext_param : org_ext_param) {
                        if (param_dict.containsKey(ext_param.getString("name"))) {
                            if (ext_param.getInteger("dtype").equals(DELIVEREXTPARAMS_EXTPARAM_TYPE_DATA_ENUM_VALUE)) {
                                String code = param_dict.getString(ext_param.getString("name"));
                                Object desc = Optional.ofNullable(enum_params.getJSONObject(ext_param.getString("name"))).map(its -> its.getString(code)).orElse("");
                                ext_params_code_map.putAll(
                                        surveyCommonService.formationExtparams(
                                                ext_param.getString("name"),
                                                desc,
                                                DELIVEREXTPARAMS_EXTPARAM_TYPE_DATA_ENUM_VALUE,
                                                code
                                        )
                                );
                            } else if (ext_param.getInteger("dtype").equals(DELIVEREXTPARAMS_EXTPARAM_TYPE_DATA_BOOL_VALUE)
                                    || ext_param.getInteger("dtype").equals(DELIVEREXTPARAMS_EXTPARAM_TYPE_DATA_TIMESTRAMP_VALUE)) {
                                ext_params_code_map.putAll(
                                        surveyCommonService.formationExtparams(
                                                ext_param.getString("name"),
                                                surveyCommonService.extparamsTransfer(ext_param, param_dict.getString(ext_param.getString("name"))),
                                                ext_param.getInteger("dtype"),
                                                null
                                        )
                                );
                            }
                        }
                    }
                }
            }
        }

        ProjectDto project = null;
        try {
            surveyCommonService.checkQrDeliverDisable(source, params);
            surveyCommonService.checkDedicatedDeliverDelete(source, params);
            surveyCommonService.checkUniqLink(project_id, null, param_dict.getJSONObject("allParams"), null);
            Integer version = param_dict.getInteger("version");
            if (!source.equals(1) && StrUtil.isNotEmpty(params.getString("deliver_id"))) {
                surveyCommonService.checkDeliverEnableRespondent(params.getString("deliver_id"));
            }
            if (!source.equals(1) && (version == null || version == 0)) {
                project = surveyCommonService.post4SurvyCommon(source, project_id, request, param_dict.getString("code"));
            }
            if (Objects.isNull(project)) {
                project = surveyGrpcConvertMapper.toProjectDto(qdesGrpcClient.getProject(project_id));
            }
            if (project.getPtype().equals(5)) {
                //如果问卷是模版，那么就设置 免费版、页脚全部露出
                footer = 0;
            } else {
                OrgInfoDto org = userGrpcClient.getOrganization(project.getCompanyId());
                footer = org.getFooter();
            }
        } catch (BusinessException e) {
            log.error("异常情况下也需要处理footer", e);
            if (Objects.isNull(project)) {
                project = surveyGrpcConvertMapper.toProjectDto(qdesGrpcClient.getProject(project_id));
            }
            if (project.getPtype().equals(5)) {
                footer = 0;
            } else {
                OrgInfoDto org = userGrpcClient.getOrganization(project.getCompanyId());
                footer = org.getFooter();
            }
            JSONObject map;
            if (!ObjectUtils.isEmpty(e.getData())) {
                map = new JSONObject(Convert.toMap(String.class, Object.class, e.getData()));
            } else {
                map = new JSONObject();
            }
            map.put("project_js", project_js_uri);
            map.put("org_edition", 1);
            map.put("footer", footer);
            return XmResultJson.success(ObjectUtils.isEmpty(e.getCode()) ? -1 : Integer.parseInt(e.getCode()), map, e.getMessage());
        } /*catch (Exception e) {
            log.error("系统不处理的异常", e);
            throw new RuntimeException(e);
        }*/

        JSONObject auth = new JSONObject();
        String scope;
        JSONObject customAttr = JSONObject.parseObject(project.getCustomAttr());
        if (customAttr.getBooleanValue("wxInfo_record")) {
            //开启微信信息记录
            auth.put("wxInfo_record", customAttr.get("wxInfo_record"));
            //授权模式
            auth.put("wxInfo_collection", customAttr.get("wxInfo_collection"));
            if (WX_INFO_COLLECTION_SILENT.getValue().equals(customAttr.getInteger("wxInfo_collection"))) {
                scope = "snsapi_base";
            } else {
                scope = "snsapi_userinfo";
            }
            auth.put("org_id", project.getCompanyId());
            //回调地址
            String org_url = surveyCommonService.getCorpUrl();
            boolean wxAuth_CEM = customAttr.getBooleanValue("wxAuth_CEM");
            String redirect_uri;
            String appid;
            if (wxAuth_CEM) {
                //第三方平台代理授权 租户把公众号相关权限授权给我们的第三方平台后，
                //第三方平台代理公众号进行网页授权，网页授权回调地址可以由我们自定义，不用去公众号后台配置
                redirect_uri = surveyWechatSetting.getRedirectUri();
                appid = customAttr.getString("wxAuth_Custom");
                auth.put("component_appid", surveyWechatSetting.getComponentAppid());
                auth.put("wxAuth_CEM", 1);
            } else {
                redirect_uri = org_url + "/api/wechat/web/auth/callback/";
                appid = surveyWechatSetting.getAppid();
                auth.put("component_appid", surveyWechatSetting.getAppid());
                auth.put("wxAuth_CEM", 0);
            }

            String url = String.format(surveyWechatSetting.getOauth2Authorize(), appid, URLEncodeUtil.encode(redirect_uri), scope);
            auth.put("wx_auth_url", url);
            log.info("{}====微信回调地址995", redirect_uri);

            //获取标识当前会话sid
            final Cookie[] cookies = request.getCookies();
            String sid = null;
            if (!ArrayUtil.isEmpty(cookies)) {
                for (Cookie cookie : cookies) {
                    if ("sid".equals(cookie.getName())) {
                        sid = cookie.getValue();
                        break;
                    }
                }
            }
            if (Objects.nonNull(sid) && sid.endsWith(customAttr.getString("wxAuth_CEM") + auth.getString("wxInfo_collection"))) {
                JSONObject wx_user_info;
                String wx_session_id;
                String old_scope;
                //如果有值证明之前已经有过答题过程,sid与当前微信服务号设置相符合则去获取授权信息
                JSONObject wxJsonObject = surveyCommonService.getWxInfoAndWxSessionIdBySid(sid);
                wx_user_info = wxJsonObject.getJSONObject("wx_user_info");
                wx_session_id = wxJsonObject.getString("wx_session_id");
                old_scope = wxJsonObject.getString("old_scope");
                log.info("got from sid: {} ========= {} ========= {}", sid, wx_user_info, old_scope);
                if (wxAuth_CEM) {
                    //通过sid获取 wx_info_id
                    if (ObjectUtils.isEmpty(wx_user_info) || StrUtil.hasEmpty(wx_user_info.getString("app_id"), wx_user_info.getString("open_id"))) {
                        JSONObject get_session_res = surveyCommonService.getUserInfoBySessionId(sid);
                        wx_user_info = get_session_res.getJSONObject("info");
                        old_scope = get_session_res.getString("scope");
                        log.info("got from grpc: {} ========= {} ========= {}", sid, wx_user_info, old_scope);
                        wx_user_info.put("channel", "微信");
                        wx_user_info.put("wx_nickname", wx_user_info.getString("nickname"));
                        wx_user_info.put("wx_sex", wx_user_info.getString("wx_sex"));
                        //保存微信相关信息， 默认过期时间30天
                        wx_session_id = UUID.randomUUID().toString();
                        //存储用户信息
                        surveyCommonService.cacheWxuserData(wx_session_id, wx_user_info, 30 * 24 * 3600 + 60);

                        JSONObject data = new JSONObject();
                        data.put("wx_session_id", wx_session_id);
                        data.put("scope", old_scope);
                        surveyCommonService.cacheWxuserData(wx_session_id, data, 30 * 24 * 3600);
                    }
                } /*else {
                    //通过sid获取 wx_info_id
                    //公用的写if上面了
                }*/

                if (wx_user_info != null && !wx_user_info.isEmpty() && StrUtil.isAllNotEmpty(wx_session_id, wx_user_info.getString("app_id"), wx_user_info.getString("open_id"))) {//获取到对应的微信信息证明
                    auth.put("wx_session_id", wx_session_id);//设置wx_session_id 则前端不会继续请求授权
                    auth.put("old_scope", old_scope);//为了前端识别之前的wx_session_id对应的授权信息时的授权模式
                }
            } else {
                //之前没有答题过程，则设置sid标记当前会话
                sid = UUID.randomUUID() + customAttr.getString("wxAuth_CEM") + auth.getString("wxInfo_collection");
                response.addCookie(new Cookie("sid", sid));
            }

            auth.put("wx_scope", scope);
            auth.put("wx_redirect_uri", redirect_uri);
            auth.put("wx_appid", appid);
            auth.put("wx_state", "123");
            auth.put("sid", sid);
        }

        JSONObject map = new JSONObject();
        map.put("project_js", project_js_uri);
        map.put("footer", footer);
        map.put("auth", auth);
        map.put("unnet", true);
        map.put("ext_params", ext_params);
        map.put("ext_params_code_map", ext_params_code_map);
        return XmResultJson.success(map);
    }

    @ApiOperation(value = "答题预览post-b")
    @PostMapping("/b/{projectId:[a-fA-F0-9]{24}}")
    public XmResultJson<?> postSurveyOpenHandler(@PathVariable(name = "projectId") String project_id,
                                                 @RequestParam(name = "source", defaultValue = "0") Integer source,
                                                 @RequestBody JSONObject param_dict,
                                                 HttpServletRequest request) {
        String param_sig;
        Integer version;
        Integer seq;
        Integer next_seq;
        ProjectDto project;

        JSONObject all_params = new JSONObject();
        source = getSource(source, param_dict);
        boolean is_preview = source.equals(RESPONDENT_SOURCE_PREVIEW_VALUE);
        if (is_preview) {
            all_params.put("source", source);
            param_sig = "";
        } else {
            all_params = Optional.ofNullable(param_dict.getJSONObject("allParams")).orElse(new JSONObject());
            param_sig = all_params.getOrDefault("sig", "").toString();
            surveyCommonService.checkDeliverParams(all_params);
            surveyCommonService.checkQrDeliverDisable(source, all_params);
            surveyCommonService.checkDedicatedDeliverDelete(source, all_params);
        }

        seq = surveyCommonService.checkUniqLink(project_id, null, param_dict.getJSONObject("allParams"), true);
        project = surveyCommonService.post4SurvyCommon(source, project_id, request, param_dict.getString("code"));

        if (is_preview) {
            version = project.getVersion();
        } else {
            String versionstr = Optional.ofNullable(request.getParameter("version")).orElse(param_dict.getString("version"));
            if (StrUtil.isNotBlank(versionstr) && Integer.parseInt(versionstr) > 0) {
                surveyCommonService.projectVersionCheck(project, Integer.parseInt(versionstr));
            }
            version = project.getReleaseVer();
        }

        if (seq > 0) {
            next_seq = seq;
        } else {
            next_seq = surveyCommonService.getNextRspdSeq(null);
        }

        //如果NotLimitTime==1，则跳过答题限制
        String notLimitTime = Optional.ofNullable(request.getParameter("NotLimitTime")).orElse(param_dict.getString("NotLimitTime"));
        if (notLimitTime == null) {
            JSONObject query = new JSONObject();
            query.put("seq", seq);
            query.put("project_id", project_id);
            SurveyRespondent rspd = surveyRespondentService.getOneRespondent(query);
            surveyCommonService.queryTimeV2(project, rspd, next_seq);
        }

        String group_code = all_params.getString("code");
        String ded_code = all_params.getString("ded_code");
        String dlvr_rid = all_params.getString("dlvr_rid");
        String idy_sid = all_params.getString("idy_sid");

        Object store = all_params.get("store__");
        JSONObject custom_store_temp = new JSONObject();
        if (!ObjectUtils.isEmpty(store)) {
            custom_store_temp = store instanceof String ? JSONObject.parseObject(store.toString()) : all_params.getJSONObject("store__");
        }
        //外部参数解密
        JSONObject custom_store = surveyCommonService.decryptExtparamters(custom_store_temp, project);
        if (!ObjectUtils.isEmpty(custom_store)) {
            custom_store.putAll(JSONObject.parseObject(param_dict.getOrDefault("ext_params", "{}").toString()));
        } else {
            custom_store = param_dict.getJSONObject("ext_params");
        }

        String deliver_id = all_params.getString("deliver_id");
        //外部参数唯一性检查
        surveyCommonService.checkExtParamsUniqueness(deliver_id, project_id, seq, custom_store, Integer.parseInt(all_params.getOrDefault("xm_custom", "0").toString()));

        String dev_id = Optional.ofNullable(request.getParameter("dev_id")).orElse(param_dict.getString("dev_id"));
        String token = createToken(project_id, version, source, next_seq, param_sig, dev_id);
        Integer status = RESPONDENT_STATUS_OPEN_VALUE;
        String ip = ServletUtil.getClientIP(request);
        String user_agent = request.getHeader("user-agent");

        JSONObject wx_user_info = new JSONObject();
        JSONObject customAttr = JSONObject.parseObject(project.getCustomAttr());
        log.info("postSurveyOpenHandler customAttr={}", customAttr.toJSONString());
        if (customAttr.getBooleanValue("wxInfo_record")) {
            String wx_session_id = Optional.ofNullable(request.getParameter("wx_session_id")).orElse(param_dict.getString("wx_session_id"));
            log.info("{}====微信用户信息 1102", wx_session_id);
            if (StrUtil.isNotBlank(wx_session_id)) {
                wx_user_info = surveyCommonService.getWxInfoBySessionId(wx_session_id);
                if (ObjectUtils.isEmpty(wx_user_info)) {
                    return XmResultJson.fail(-1, "微信授权失败");
                }
            }
        }

        //微信访问检查控制
        surveyCommonService.wxCheck(source, wx_user_info.getString("open_id"), wx_user_info.getString("app_id"), project);
        List<Integer> ruleList = new ArrayList<>();
        //redis set
        String key = String.format(RSPD_FORMAT, next_seq);
        redisService.hashSet(key, "pid", project_id);
        redisService.hashSet(key, "begin_time", DateUtil.currentSeconds());
        redisService.hashSet(key, "version", version);
        if (StrUtil.isNotBlank(group_code)) {
            redisService.hashSet(key, "group_code", group_code);
        }
        if (StrUtil.isNotBlank(dev_id)) {
            redisService.hashSet(key, "person_id", dev_id);
        }
        if (!ObjectUtils.isEmpty(custom_store)) {
            custom_store.forEach((k, v) -> {
                if (v.toString().equalsIgnoreCase("true") || v.toString().equalsIgnoreCase("false")) {
                    redisService.hashSet(key, "custom_data__" + k, v.toString());
                }
            });
        }
        redisService.hashSet(key, "source", source);
        redisService.hashSet(key, "status", status);
        if (StrUtil.isNotBlank(dlvr_rid)) {
            redisService.hashSet(key, "dlvr_rid", dlvr_rid);
        }
        if (StrUtil.isNotBlank(ded_code)) {
            redisService.hashSet(key, "ded_code", ded_code);
        }
        if (StrUtil.isNotBlank(idy_sid)) {
            redisService.hashSet(key, "idy_sid", idy_sid);
        }
        if (!ObjectUtils.isEmpty(wx_user_info)) {
            redisService.hashSet(key, "wx_user_info", wx_user_info);
        }

        JSONObject deliver_data = new JSONObject();
        List<String> keys = Lists.newArrayList("deliver_g_id", "deliver_id", "member_id", "again_deliver_record_id");
        JSONObject finalAll_params = all_params;
        keys.forEach(dkey -> {
            Object val;
            if ("member_id".equals(dkey)) {
                val = finalAll_params.getOrDefault("memberId", "");
            } else {
                val = finalAll_params.get(dkey);
            }
            if (val != null) {
                deliver_data.put(dkey, val);
            }
        });
        if (!ObjectUtils.isEmpty(deliver_data)) {
            redisService.hashSetAll(key, deliver_data);
        }
        JSONObject info_data = new JSONObject();
        if (StrUtil.isNotBlank(user_agent)) {
            info_data.put("user_agent", user_agent);
        }
        if (StrUtil.isNotBlank(ip)) {
            info_data.put("ip", ip);
        }
        if (!ObjectUtils.isEmpty(info_data)) {
            redisService.hashSetAll(key, info_data);
        }
        if (customAttr.getBooleanValue("ruleOnce")) {
            ruleList = surveyCommonService.qdesGetQlistWithPid(project_id, is_preview);
            log.error("postSurveyOpenHandler qdesGetQlistWithPid ret={}", JSONArray.toJSONString(ruleList));
            redisService.hashSet(key, "gnt_list", ruleList);

            //调试用,实在找不到原因了
            //redisService.hashSet(key, "fuck_gnt_list_1", JSONArray.toJSONString(ruleList));
            //JSONArray jsonArray = new JSONArray();
            //jsonArray.addAll(ruleList);
            //redisService.hashSet(key, "fuck_gnt_list_2", jsonArray);
        }
        //没有接收者，写他干叼
        JSONObject open_data = new JSONObject();
        open_data.put("project_id", project_id);
        open_data.put("seq", next_seq);
        open_data.put("member_id", all_params.getOrDefault("memberId", ""));
        open_data.put("deliver_id", all_params.getOrDefault("deliver_id", ""));
        open_data.put("deliver_group_id", all_params.getOrDefault("deliver_g_id", ""));
        open_data.put("deliver_g_id", all_params.getOrDefault("deliver_g_id", ""));
        open_data.put("source", source);
        open_data.put("status", status);
        if (!ObjectUtils.isEmpty(all_params.get("again_deliver_record_id"))) {
            open_data.put("again_deliver_record_id", all_params.get("again_deliver_record_id"));
        }

        surveyMessageSendService.surveyRespondentOpenSend(open_data);
        //await redis_mq.publish(topics.survey_respondent_open, open_data)
        JSONObject ret = new JSONObject();
        ret.put("seq", next_seq);
        ret.put("jwt_token", token);
        ret.put("version", version);
        ret.put("ruleList", ruleList);
        return XmResultJson.success(ret);
    }

    @ApiOperation(value = "答题预览post-b2")
    @PostMapping("/b2/{projectId:[a-fA-F0-9]{24}}")
    public XmResultJson<?> postSurveyBeginHandler(@PathVariable(name = "projectId") String project_id,
                                                  @RequestParam(name = "source", defaultValue = "0") Integer source,
                                                  @RequestBody JSONObject param_dict,
                                                  HttpServletRequest request) {
        //b接口返回的jwt_token字段会带到这里，需要处理token获取seq等
        String authorization = request.getHeader("authorization");
        Map<String, Object> current = checkCommon(project_id, source, param_dict, authorization);

        Integer seq = (Integer) current.get("seq");
        SurveyRespondent rspd = checkRespondent(project_id, seq);

        //如果NotLimitTime==1，则跳过答题限制
        String notLimitTime = Optional.ofNullable(request.getParameter("NotLimitTime")).orElse(param_dict.getString("NotLimitTime"));
        if (notLimitTime == null) {
            ProjectDto project = surveyGrpcConvertMapper.toProjectDto(qdesGrpcClient.getProject(project_id));
            surveyCommonService.queryTimeV2(project, rspd, seq);
        }

        Integer status = RESPONDENT_STATUS_BEGIN.getValue();
        String key = String.format(RSPD_FORMAT, seq);
        Integer old_status = surveyCommonService.getRspdStatus(seq);
        redisService.hashSet(key, "begin_time", DateUtil.currentSeconds());
        redisService.hashSet(key, "status", status);
        redisService.setAdd("seq_" + project_id, seq);
        redisService.setAdd("answered_pid", project_id);

        if (!status.equals(old_status)) {

            //data = {
            //        'project_id': project_id,
            //        'version': current['ver'],
            //        'seq': seq,
            //        'time': '',
            //        'prev': old_status,
            //        'current': status,
            //}
            JSONObject mqData = new JSONObject();
            mqData.put("project_id", project_id);
            mqData.put("version", current.get("ver"));
            mqData.put("seq", seq);
            mqData.put("prev", old_status);
            mqData.put("current", status);

            surveyMessageSendService.surveyRespondentStatusChangedSend(mqData);
            //await redis_mq.publish(topics.survey_respondent_status_changed, data)
        }
        return XmResultJson.success(new JSONObject());
    }

    @ApiOperation(value = "答题预览post-c")
    @PostMapping("/c/{projectId:[a-fA-F0-9]{24}}")
    public XmResultJson<?> postSurveyContinueHandler(@PathVariable(name = "projectId") String project_id,
                                                     @RequestParam(name = "source", defaultValue = "0") Integer source,
                                                     @RequestBody JSONObject param_dict,
                                                     HttpServletRequest request) throws BusinessException {
        //b接口返回的jwt_token字段会带到这里，需要处理token获取seq等
        String authorization = request.getHeader("authorization");
        Map<String, Object> current = checkCommon(project_id, source, param_dict, authorization);

        Integer seq = (Integer) current.get("seq");
        SurveyRespondent rspd = checkRespondent(project_id, seq);

        Integer version = (Integer) current.get("ver");
        source = (Integer) current.get("src");

        Integer old_seq = surveyCommonService.checkUniqLink(project_id, seq, param_dict.getJSONObject("allParams"), null);
        if (old_seq != null && old_seq > 0 && !seq.equals(old_seq)) {
            log.error("/c/ 2invalid seq=");
            //旧唯一投放重新开始
            throw new BusinessException(SurveyErrorCodeEnum.INVALID_SEQ.getValue(), "2invalid seq=" + seq);
        }
        ProjectDto project = surveyCommonService.post4SurvyCommon(source, project_id, request, param_dict.getString("code"));
        if (!source.equals(RESPONDENT_SOURCE_PREVIEW_VALUE)) {
            surveyCommonService.projectVersionCheck(project, version);
        } else {
            surveyCommonService.projectVersionCheckPreview(project, version);
        }

        if (surveyCommonService.getRspdCreated(seq) == null) {
            log.error("/c/ 答卷开始时间记录已过期，请重新开始");
            //旧答卷开始时间记录在缓存，会过期，过期后强制重新开始
            throw new BusinessException(SurveyErrorCodeEnum.SURVEY_RESTART.getValue(), "答卷开始时间记录已过期，请重新开始");
        }

        //如果NotLimitTime==1，则跳过答题限制
        String notLimitTime = Optional.ofNullable(request.getParameter("NotLimitTime")).orElse(param_dict.getString("NotLimitTime"));
        if (notLimitTime == null) {
            surveyCommonService.queryTimeV2(project, rspd, seq);
        }

        //微信访问检查控制
        JSONObject customAttr = JSONObject.parseObject(project.getCustomAttr());
        if (customAttr.getBooleanValue("wxOnce")) {
            JSONObject wx_user_info = surveyCommonService.getRspdWxInfo(seq);
            surveyCommonService.wxCheck(source, wx_user_info.getString("open_id"), wx_user_info.getString("app_id"), project);
        }

        String ip = ServletUtil.getClientIP(request);
        String user_agent = request.getHeader("user-agent");
        Integer get_status = surveyCommonService.getRspdStatus(seq);
        JSONObject info_data = new JSONObject();
        if (StrUtil.isNotBlank(user_agent)) {
            info_data.put("user_agent", user_agent);
        }
        if (StrUtil.isNotBlank(ip)) {
            info_data.put("ip", ip);
        }
        if (!ObjectUtils.isEmpty(info_data)) {
            String key = String.format(RSPD_FORMAT, seq);
            redisService.hashSetAll(key, info_data);
        }

        if (get_status != null && get_status.equals(RESPONDENT_STATUS_OPEN_VALUE)) {
            //不显示继续答题按钮的情况下，不能调用继续答题
            throw new BusinessException(SurveyErrorCodeEnum.INVALID_SEQ.getValue(), "3invalid SEQ=" + seq);
        }

        JSONObject jsonret = surveyCommonService.getAnsweredAnswers(project_id, seq);
        JSONObject old_answers = jsonret.getJSONObject("answered_answers");
        JSONObject custom_answers = jsonret.getJSONObject("custom_answers");
        JSONArray question_list = jsonret.getJSONArray("question_list");
        String old_answers_str = ObjectUtils.isEmpty(old_answers) ? "{}" : old_answers.toJSONString();

        JSONArray old_page_qid_list = surveyCommonService.getLastAnsweredPageQidlist(seq);
        Object question_order = surveyCommonService.getNewQuestionOrder(project_id, seq);
        JSONArray sorts = surveyCommonService.getSortsBySeq(seq);
        JSONArray ruleList = surveyCommonService.getRspdGntList(seq);

        JSONObject result = new JSONObject();
        result.put("old_page_qid_list", old_page_qid_list);
        result.put("old_answers_str", old_answers_str);
        result.put("question_order", question_order);
        result.put("version", version);
        result.put("sorts", sorts);
        result.put("seq", seq);
        result.put("custom_data", custom_answers);
        result.put("ruleList", ObjectUtil.isEmpty(ruleList) ? null : ruleList);
        result.put("question_list", question_list);
        return XmResultJson.success(result);
    }

    @ApiOperation(value = "答题下一页")
    @PostMapping("/n/{projectId:[a-fA-F0-9]{24}}")
    public XmResultJson<?> postSurveyNextHandler(@PathVariable(name = "projectId") String project_id,
                                                 @RequestParam(name = "source", defaultValue = "0") Integer source,
                                                 @RequestBody String param_dict_str,
                                                 HttpServletRequest request) {
        JSONObject param_dict = JSONObject.parseObject(param_dict_str);
        //b接口返回的jwt_token字段会带到这里，需要处理token获取seq等
        String authorization = request.getHeader("authorization");
        Map<String, Object> current = checkToken(authorization, project_id);

        source = getSource(source, param_dict);
        ProjectDto project = surveyCommonService.post4SurvyCommon(source, project_id, request, param_dict.getString("code"));

        //如果NotLimitTime==1，则跳过答题限制
        String notLimitTime = Optional.ofNullable(request.getParameter("NotLimitTime")).orElse(param_dict.getString("NotLimitTime"));
        JSONObject all_params = Optional.ofNullable(param_dict.getJSONObject("allParams")).orElse(new JSONObject());

        log.info("n paramMap={}", all_params.toJSONString());
        surveyCommonService.checkDedicatedDeliverDelete(source, all_params);
        surveyCommonService.checkQrDeliverDisable(source, all_params);

        Integer seq = (Integer) current.get("seq");
        SurveyRespondent rspd = checkRespondent(project_id, seq);

        if (notLimitTime == null) {
            surveyCommonService.queryTimeV2(project, rspd, seq);
        }

        //微信访问检查控制
        JSONObject customAttr = JSONObject.parseObject(project.getCustomAttr());
        if (customAttr.getBooleanValue("wxOnce")) {
            JSONObject wx_user_info = surveyCommonService.getRspdWxInfo(seq);
            surveyCommonService.wxCheck(source, wx_user_info.getString("open_id"), wx_user_info.getString("app_id"), project);
        }

        //问卷4.0 黑白名单检查
        surveyCommonService.checkQuestionNameList(param_dict);
        Integer version = (Integer) current.get("ver");
        source = (Integer) current.get("src");

        boolean is_preview = source.equals(RESPONDENT_SOURCE_PREVIEW_VALUE);
        String param_sig;
        int data_status;
        if (is_preview) {
            data_status = RESPONDENT_DATA_STATUS_TEST_VALUE;
            param_sig = "";
        } else {
            data_status = RESPONDENT_DATA_STATUS_FORMAL_VALUE;
            param_sig = all_params.getOrDefault("sig", "").toString();
            surveyCommonService.checkDeliverParams(all_params);
        }

        //检查参数签名，签名不同任务投放参数不同，即不同的投放链接
        if (!StrUtil.equals(param_sig, current.get("psig").toString())) {
            throw new BusinessException(SurveyErrorCodeEnum.INVALID_SEQ.getValue(), "invalid psig=" + param_sig);
        }

        String member_id = all_params.getString("memberId");
        String check_code = all_params.getString("checkCode");
        String check_ext_code = all_params.getString("xm_ext_uniq");
        String deliver_g_id = all_params.getString("deliver_g_id");
        String deliver_id = all_params.getString("deliver_id");
        String again_deliver_record_id = all_params.getString("again_deliver_record_id");
        String dlvr_rid = all_params.getString("dlvr_rid");
        String group_code = param_dict.getString("code");

        int check_submit = NumberUtil.isNumber(all_params.getString("checkSubmit")) ? all_params.getInteger("checkSubmit") : 0;
        String deliver_key = null;
        String deliver_hkey = null;

        if (check_submit > 0 && StrUtil.isNotBlank(check_code)) {
            deliver_key = String.format(DLV_CHK_FORMAT, project_id);
            if (StrUtil.isNotBlank(deliver_g_id)) {
                deliver_hkey = deliver_g_id + "_" + check_code;
            } else if (source.equals(RESPONDENT_SOURCE_WECHATMP_VALUE)) {
                deliver_hkey = deliver_id + "_" + check_code;
            } else {
                deliver_hkey = check_code;
            }

            int old_seq;
            try {
                Object o = redisService.hashGet(deliver_key, deliver_hkey);
                old_seq = Integer.parseInt(o.toString());
            } catch (Exception e) {
                log.error("invalid SEQ 1111");
                throw new BusinessException(SurveyErrorCodeEnum.INVALID_SEQ.getValue(), "invalid SEQ=" + seq);
            }
            if (old_seq == 0) {
                log.error("already submmited 1111");
                throw new BusinessException(SurveyErrorCodeEnum.ALREADY_ANSWERED.getValue(), "already submmited");
            }
            if (old_seq != seq) {
                log.error("invalid SEQ 2222");
                throw new BusinessException(SurveyErrorCodeEnum.INVALID_SEQ.getValue(), "invalid SEQ=" + seq);
            }
        }

        String ext_key = null;
        if (StrUtil.isNotBlank(check_ext_code)) {
            ext_key = String.format(DLV_EXT_CHK_FORMAT, project_id, check_ext_code);
            int old_seq;
            try {
                Object o = redisService.get(ext_key);
                old_seq = Integer.parseInt(o.toString());
            } catch (Exception e) {
                log.error("invalid SEQ 3333");
                throw new BusinessException(SurveyErrorCodeEnum.INVALID_SEQ.getValue(), "invalid SEQ=" + seq);
            }
            if (old_seq == 0) {
                log.error("already submmited 2222");
                throw new BusinessException(SurveyErrorCodeEnum.ALREADY_ANSWERED.getValue(), "already submmited");
            }
            if (old_seq != seq) {
                log.error("invalid SEQ 44444");
                throw new BusinessException(SurveyErrorCodeEnum.INVALID_SEQ.getValue(), "invalid SEQ=" + seq);
            }
        }

        JSONObject page = param_dict.getJSONObject("page");
        String page_id;
        JSONArray page_qid_list;
        if (!ObjectUtils.isEmpty(page)) {
            page_id = page.getString("gid");
            if (ObjectUtils.isEmpty(page_id)) {
                log.error(":page.gid is required");
                throw new BusinessException(String.valueOf(ReqErrorCodeEnum.PARAM_FORMAT.getCode()), ":page.gid is required");
            }
            Object tmp = page.get("qid_list");
            if (ObjectUtils.isEmpty(tmp)) {
                log.error(":page.qid_list is required");
                throw new BusinessException(String.valueOf(ReqErrorCodeEnum.PARAM_FORMAT.getCode()), ":page.qid_list is required");
            }

            if (tmp instanceof List) {
                page_qid_list = (JSONArray) tmp;
            } else {
                if (JSONUtil.isTypeJSONArray(tmp.toString())) {
                    page_qid_list = JSONArray.parseArray(tmp.toString());
                } else {
                    page_qid_list = new JSONArray();
                    page_qid_list.add(tmp.toString());
                }
            }
        } else {
            page_id = param_dict.getString("page_qid_list");
            page_qid_list = new JSONArray();
        }

        Object page_answers_tmp = param_dict.get("answer");
        JSONArray question_list = Optional.ofNullable(param_dict.getJSONArray("question_list")).orElse(new JSONArray());
        boolean check_goback = param_dict.getBooleanValue("clickPrev");

        JSONObject page_answers;
        if (page_answers_tmp instanceof String) {
            page_answers = JSONObject.parseObject(page_answers_tmp.toString());
        } else {
            page_answers = (JSONObject) page_answers_tmp;
        }

        JSONArray uniq_check_list = param_dict.getJSONArray("uniq_check_list");
        if (!ObjectUtils.isEmpty(uniq_check_list)) {
            surveyCommonService.saveUniqAnswers(project_id, seq, page_answers, uniq_check_list, false);
        }

        JSONArray custom_data_action = param_dict.getJSONArray("custom_data_action");
        surveyCommonService.getCustomData(project_id, custom_data_action);

        surveyCommonService.savePageAnswers(
                project_id,
                seq,
                page_answers,
                page_id,
                page_qid_list,
                version,
                check_goback,
                param_dict.get("custom_data"),
                question_list
        );

        String quota_check = surveyCommonService.getQuotaCheckpointsForPageAnswers(project_id, page_answers);

        JSONObject retData = surveyCommonService.getAnsweredAnswers(project_id, seq);
        JSONObject answered_answers = retData.getJSONObject("answered_answers");
        JSONObject custom_answers = retData.getJSONObject("custom_answers");
        question_list = retData.getJSONArray("question_list");

        //外部参数唯一性检查
        surveyCommonService.checkExtParamsUniqueness(deliver_id, project_id, seq, custom_answers, Integer.parseInt(all_params.getOrDefault("xm_custom", "0").toString()));

        String user_agent = ObjectUtils.isEmpty(request.getHeader("user-agent")) ? "" : request.getHeader("user-agent");
        int client_type = surveyCommonService.getClientType(user_agent);

        boolean quotafull = surveyCommonService.doCheckClientTypeQuotafull(client_type, project_id);
        if (!quotafull && !ObjectUtils.isEmpty(quota_check)) {
            Pair<Boolean, List<SurveyQuotaCheckItemVo>> retjson = surveyCommonService.isQuotafull(project_id, seq, answered_answers, null);
            quotafull = retjson.getValue0();
            //List<SurveyQuotaCheckItemVo> quota_checkitem_list = retjson.getValue1();
        }

        if (quotafull) {
            log.error("/n/ 配额已满");
            int status = RESPONDENT_STATUS_QUOTA_FULL_VALUE;
            String group_id = surveyCommonService.getGroupIdOnSurvey(group_code, project);

            JSONObject paramMap = new JSONObject();
            //函数体就要一个project_id
            paramMap.put("project_id", project.getId());
            paramMap.put("seq", seq);
            paramMap.put("version", version);
            paramMap.put("status", status);
            paramMap.put("data_status", data_status);
            paramMap.put("source", source);
            paramMap.put("ip", ServletUtil.getClientIP(request));
            paramMap.put("group_code", group_code);
            paramMap.put("total_answer", answered_answers);
            paramMap.put("brand_info", Optional.ofNullable(param_dict.getString("brand_info")).orElse(""));
            paramMap.put("model_info", Optional.ofNullable(param_dict.getString("model_info")).orElse(""));
            paramMap.put("user_agent", user_agent);
            paramMap.put("person_id", current.get("did"));
            paramMap.put("member_id", member_id);
            paramMap.put("deliver_group_id", deliver_g_id);
            paramMap.put("deliver_id", deliver_id);
            paramMap.put("uniq_check_list", param_dict.getJSONArray("uniq_qid_list"));
            paramMap.put("custom_answer", custom_answers);
            paramMap.put("again_deliver_record_id", again_deliver_record_id);
            paramMap.put("group_id", group_id);
            paramMap.put("question_list", question_list);
            paramMap.put("dlvr_rid", dlvr_rid);

            log.info("n paramMap={}", paramMap.toJSONString());
            Pair<SurveyRespondent, Boolean> retjson = surveyCommonService.submitRespondent(paramMap);
            boolean is_new = retjson.getValue1();
            SurveyRespondent respondent = retjson.getValue0();
            log.info("/n/ is_new={}", is_new);
            if (ObjectUtils.isEmpty(respondent)) {
                throw new BusinessException(SurveyErrorCodeEnum.REPEAT_RECOVERY.getValue(), "该答卷已经提交,seq=" + seq);
            }

            Integer filter_status = surveyCommonService.checkAutoFilter(project_id, answered_answers, source, group_id, custom_answers);
            if (Objects.equals(filter_status, RESPONDENT_STATUS_DISCARD_VALUE)) {
                respondent = surveyCommonService.discardAutoFilter(respondent);
            }

            surveyCommonService.saveRespondentpropDelay(
                    respondent,
                    project_id,
                    seq,
                    answered_answers,
                    deliver_id,
                    deliver_g_id,
                    member_id,
                    custom_answers
            );

            surveyCommonService.mqSurveyRespondentSubmit(respondent, answered_answers, custom_answers, null);

            if (is_new && StrUtil.isNotBlank(deliver_key)) {
                redisService.hashSet(deliver_key, deliver_hkey, "0");
            }

            if (is_new && StrUtil.isNotBlank(check_ext_code)) {
                redisService.set(ext_key, "0");
            }

            JSONObject ret_data = new JSONObject();
            ret_data.put("survey_id", project_id);
            ret_data.put("seq", seq);
            ret_data.put("status", status);
            ret_data.put("source", source);
            ret_data.put("survey_ip", ServletUtil.getClientIP(request));
            ret_data.put("ts", DateUtil.currentSeconds());
            if (StrUtil.isNotBlank(deliver_id)) {
                ret_data.put("deliver_id", deliver_id);
            }
            if (StrUtil.isNotBlank(deliver_g_id)) {
                ret_data.put("deliver_group_id", deliver_g_id);
            }
            if (StrUtil.isNotBlank(again_deliver_record_id)) {
                ret_data.put("again_deliver_record_id", again_deliver_record_id);
            }
            if (respondent.getFinishTime() != null && respondent.getBeginTime() != null) {
                ret_data.put("timecost", LocalDateTimeUtil.between(respondent.getBeginTime(), respondent.getFinishTime(), ChronoUnit.SECONDS));
            }
            ret_data.put("sig", SignUtil.genSign(ret_data, surveyCommonService.getSurveySecret()));
            return XmResultJson.success(ret_data);
        }
        return XmResultJson.success(new JSONObject());
    }


    @ApiOperation(value = "答卷提交")
    @PostMapping("/s/{projectId:[a-fA-F0-9]{24}}")
    public XmResultJson<?> postSurveySubmitHandler(@PathVariable(name = "projectId") String project_id,
                                                   @RequestParam(name = "source", defaultValue = "0") Integer source,
                                                   @RequestBody JSONObject param_dict,
                                                   HttpServletRequest request) {
        String uu = new ObjectId().toHexString();
        //b接口返回的jwt_token字段会带到这里，需要处理token获取seq等
        String authorization = request.getHeader("authorization");
        Map<String, Object> current = checkToken(authorization, project_id);

        if (ObjectUtil.hasEmpty(current.get("src"), current.get("ver"), current.get("seq")) || !Objects.equals(source, current.get("src"))) {
            throw new BusinessException(String.valueOf(ReqErrorCodeEnum.INVALID_TOKEN.getCode()), "invalid token" + JSON.toJSONString(current));
        }
        source = (Integer) current.get("src");
        Integer version = (Integer) current.get("ver");
        Integer seq = (Integer) current.get("seq");

        ProjectDto project = surveyCommonService.post4SurvyCommon(source, project_id, request, param_dict.getString("code"));

        //如果NotLimitTime==1，则跳过答题限制
        String notLimitTime = Optional.ofNullable(request.getParameter("NotLimitTime")).orElse(param_dict.getString("NotLimitTime"));
        JSONObject all_params = Optional.ofNullable(param_dict.getJSONObject("allParams")).orElse(new JSONObject());

        log.info("s allParams={}", all_params.toJSONString());
        surveyCommonService.checkDedicatedDeliverDelete(source, all_params);
        surveyCommonService.checkQrDeliverDisable(source, all_params);

        SurveyRespondent rspd = checkRespondent(project_id, seq);

        boolean deliver_disable = surveyCommonService.checkQrDeliverDisableV2(source, all_params);
        if (!deliver_disable) {
            throw new BusinessException(SurveyErrorCodeEnum.LINK_DISABLE.getValue(), "该链接已失效");
        }

        if (notLimitTime == null) {
            surveyCommonService.queryTimeV2(project, rspd, seq);
        }

        JSONObject wx_user_info = new JSONObject();
        String user_agent = request.getHeader("user-agent");
        String session_id = all_params.getOrDefault("token_id", "").toString();
        String session_type = all_params.getOrDefault("session_type", "").toString();
        if (StrUtil.isNotBlank(session_id) && StrUtil.isNotBlank(session_type) && Objects.equals("1", session_type)) {
            if (StrUtil.isNotBlank(session_id)) {
                JSONObject userinfo = surveyCommonService.getWxworkUserDetail(session_id);
                if (!ObjectUtils.isEmpty(userinfo)) {
                    wx_user_info.put("channel", "企业微信");
                    wx_user_info.put("avatar", userinfo.get("avatar"));
                    wx_user_info.put("department", userinfo.get("department"));
                    wx_user_info.put("corp_name", userinfo.get("corp_name"));
                    wx_user_info.put("email", userinfo.get("email"));
                    wx_user_info.put("mobile", userinfo.get("mobile"));
                    wx_user_info.put("wx_nickname", userinfo.get("wx_nickname"));
                    wx_user_info.put("wx_sex", userinfo.get("gender"));
                    wx_user_info.put("open_id", userinfo.get("open_userid"));
                    surveyCommonService.setRspdWxInfo(seq, wx_user_info);
                }
            }
        } else {
            //微信访问检查控制
            wx_user_info = surveyCommonService.getRspdWxInfo(seq);
            surveyCommonService.wxCheck(source, wx_user_info.getString("open_id"), wx_user_info.getString("app_id"), project);
            if (StrUtil.isNotBlank(session_id) && StrUtil.isNotBlank(session_type) && Objects.equals("0", session_type)) {
                JSONObject user_info = surveyCommonService.getWxSessionInfo(session_id);
                wx_user_info.put("email", user_info.get("email"));
                wx_user_info.put("channel", user_info.get("channel"));
                if (StrUtil.contains(user_agent, "wxwork")) {
                    wx_user_info.put("channel", "企业微信");
                }
                if (ObjectUtils.isEmpty(wx_user_info.get("open_id"))) {
                    wx_user_info.put("open_id", user_info.get("open_id"));
                }
                if (ObjectUtils.isEmpty(wx_user_info.get("wx_nickname"))) {
                    wx_user_info.put("wx_nickname", user_info.get("userName"));
                }
                if (ObjectUtils.isEmpty(wx_user_info.get("mobile"))) {
                    wx_user_info.put("mobile", user_info.get("mobile"));
                }
                surveyCommonService.setRspdWxInfo(seq, wx_user_info);
            }
        }

        //问卷4.0 黑白名单检查
        surveyCommonService.checkQuestionNameList(param_dict);
        Integer status = param_dict.getInteger("status");

        boolean is_preview = source.equals(RESPONDENT_SOURCE_PREVIEW_VALUE);
        String param_sig;
        int data_status;
        if (is_preview) {
            data_status = RESPONDENT_DATA_STATUS_TEST_VALUE;
            param_sig = "";
        } else {
            data_status = RESPONDENT_DATA_STATUS_FORMAL_VALUE;
            param_sig = all_params.getOrDefault("sig", "").toString();
            surveyCommonService.checkDeliverParams(all_params);
        }

        //检查参数签名，签名不同任务投放参数不同，即不同的投放链接
        if (!StrUtil.equals(param_sig, current.get("psig").toString())) {
            throw new BusinessException(SurveyErrorCodeEnum.INVALID_SEQ.getValue(), "invalid psig=" + param_sig);
        }

        String member_id = all_params.getString("memberId");
        int check_submit = NumberUtil.isNumber(all_params.getString("checkSubmit")) ? all_params.getInteger("checkSubmit") : 0;
        String check_code = all_params.getString("checkCode");
        String check_ext_code = all_params.getString("xm_ext_uniq");
        String deliver_g_id = all_params.getString("deliver_g_id");
        String deliver_id = all_params.getString("deliver_id");
        String again_deliver_record_id = all_params.getString("again_deliver_record_id");
        String group_code = param_dict.getString("code");

        Object total_answer_str = param_dict.get("answer");
        Object custom_answer_str = param_dict.get("custom_data");
        Object store_answer_str = param_dict.get("store__");

        String dlvr_rid = all_params.getString("dlvr_rid");
        JSONArray question_list = param_dict.getJSONArray("question_list");

        JSONObject total_answer_temp = null;
        JSONObject custom_answer = null;
        JSONObject store_answer_temp = new JSONObject();
        JSONObject store_answer;
        try {
            if (!ObjectUtils.isEmpty(total_answer_str)) {
                if (total_answer_str instanceof String) {
                    total_answer_temp = JSONObject.parseObject(total_answer_str.toString());
                } else {
                    total_answer_temp = (JSONObject) total_answer_str;
                }
            }

            if (!ObjectUtils.isEmpty(custom_answer_str)) {
                if (custom_answer_str instanceof String) {
                    custom_answer = JSONObject.parseObject(custom_answer_str.toString());
                } else {
                    custom_answer = (JSONObject) custom_answer_str;
                }
            }
            if (!ObjectUtils.isEmpty(store_answer_str)) {
                store_answer_temp = store_answer_str instanceof String ? JSONObject.parseObject(store_answer_str.toString()) : all_params.getJSONObject("store__");
            }
            //外部参数解密
            store_answer = surveyCommonService.decryptExtparamters(store_answer_temp, project);
        } catch (Exception e) {
            throw new BusinessException(String.valueOf(ReqErrorCodeEnum.PARAM_FORMAT.getCode()), ":answer is invalid json");
        }

        JSONObject total_answer = new JSONObject();
        if (!ObjectUtils.isEmpty(total_answer_temp)) {
            total_answer.putAll(total_answer_temp);
        }
        if (!ObjectUtils.isEmpty(custom_answer)) {
            custom_answer.putAll(store_answer);
        } else {
            custom_answer = store_answer;
        }

        JSONObject answers = surveyCommonService.getAnsweredData(seq);
        String prefix = "custom_data__";
        JSONObject custom_answers = new JSONObject();
        answers.forEach((k, v) -> {
            if (StrUtil.startWith(k, prefix)) {
                custom_answers.put(k, v);
            }
        });
        custom_answers.putAll(custom_answer);
        //订制投放外部参数
        custom_answers.putAll(Optional.ofNullable(param_dict.getJSONObject("ext_params")).orElse(new JSONObject()));
        custom_answer = new JSONObject();
        custom_answer.putAll(custom_answers);

        //外部参数唯一性检查
        surveyCommonService.checkExtParamsUniqueness(deliver_id, project_id, seq, custom_answers, Integer.parseInt(all_params.getOrDefault("xm_custom", "0").toString()));

        String deliver_key = null;
        String deliver_hkey = null;
        if (check_submit > 0 && StrUtil.isNotBlank(check_code)) {
            deliver_key = String.format(DLV_CHK_FORMAT, project_id);
            if (StrUtil.isNotBlank(deliver_g_id)) {
                deliver_hkey = deliver_g_id + "_" + check_code;
            } else if (source.equals(RESPONDENT_SOURCE_WECHATMP_VALUE)) {
                deliver_hkey = deliver_id + "_" + check_code;
            } else {
                deliver_hkey = check_code;
            }

            int old_seq;
            try {
                Object o = redisService.hashGet(deliver_key, deliver_hkey);
                old_seq = Integer.parseInt(o.toString());
            } catch (Exception e) {
                throw new BusinessException(SurveyErrorCodeEnum.INVALID_SEQ.getValue(), "invalid SEQ=" + seq);
            }
            if (old_seq == 0) {
                throw new BusinessException(SurveyErrorCodeEnum.ALREADY_ANSWERED.getValue(), "already submmited");
            }
            if (old_seq != seq) {
                throw new BusinessException(SurveyErrorCodeEnum.INVALID_SEQ.getValue(), "invalid SEQ=" + seq);
            }
        }

        String ext_key = null;
        if (StrUtil.isNotBlank(check_ext_code)) {
            ext_key = String.format(DLV_EXT_CHK_FORMAT, project_id, check_ext_code);
            int old_seq;
            try {
                Object o = redisService.get(ext_key);
                old_seq = Integer.parseInt(o.toString());
            } catch (Exception e) {
                throw new BusinessException(SurveyErrorCodeEnum.INVALID_SEQ.getValue(), "invalid SEQ=" + seq);
            }
            if (old_seq == 0) {
                throw new BusinessException(SurveyErrorCodeEnum.ALREADY_ANSWERED.getValue(), "already submmited");
            }
            if (old_seq != seq) {
                throw new BusinessException(SurveyErrorCodeEnum.INVALID_SEQ.getValue(), "invalid SEQ=" + seq);
            }
        }

        String group_id = surveyCommonService.getGroupIdOnSurvey(group_code, project);
        String ip = ServletUtil.getClientIP(request);
        JSONObject paramMap = new JSONObject();
        //函数体就要一个project_id
        paramMap.put("project_id", project.getId());
        paramMap.put("seq", seq);
        paramMap.put("version", version);
        paramMap.put("status", status);
        paramMap.put("data_status", data_status);
        paramMap.put("source", source);
        paramMap.put("ip", ip);
        paramMap.put("group_code", group_code);
        paramMap.put("total_answer", total_answer);
        paramMap.put("brand_info", Optional.ofNullable(param_dict.getString("brand_info")).orElse(""));
        paramMap.put("model_info", Optional.ofNullable(param_dict.getString("model_info")).orElse(""));
        paramMap.put("user_agent", user_agent);
        paramMap.put("person_id", current.get("did"));
        paramMap.put("member_id", member_id);
        paramMap.put("deliver_group_id", deliver_g_id);
        paramMap.put("deliver_id", deliver_id);
        paramMap.put("uniq_check_list", param_dict.getJSONArray("uniq_qid_list"));
        paramMap.put("custom_answer", custom_answer);
        paramMap.put("again_deliver_record_id", again_deliver_record_id);
        paramMap.put("custom_data_action", param_dict.get("custom_data_action"));
        paramMap.put("group_id", group_id);
        paramMap.put("question_list", question_list);
        paramMap.put("uu", uu);
        paramMap.put("dlvr_rid", dlvr_rid);

        log.info("s paramMap={}", paramMap.toJSONString());
        Pair<SurveyRespondent, Boolean> retjson = surveyCommonService.submitRespondent(paramMap);
        boolean is_new = retjson.getValue1();
        SurveyRespondent respondent = retjson.getValue0();
        log.info("/s/ is_new={}", is_new);
        if (ObjectUtils.isEmpty(respondent)) {
            throw new BusinessException(SurveyErrorCodeEnum.REPEAT_RECOVERY.getValue(), "该答卷已经提交,seq=" + seq);
        }

        try {
            //检查自动删除器
            Integer filter_status = surveyCommonService.checkAutoFilter(project_id, total_answer, source, group_id, custom_answers);
            //式答题时 配额检查&更新
            status = respondent.getStatus();//自动删除器 触发，status状态为7

            boolean formal_flag = Objects.equals(status, RESPONDENT_STATUS_FINISH_VALUE) && Objects.equals(data_status, RESPONDENT_DATA_STATUS_FORMAL_VALUE);
            log.info("s status={}", status);
            log.info("s data_status={}", data_status);
            log.info("s formal_flag={}", formal_flag);
            if (formal_flag) {
                Integer check_status = status;
                check_status = postCheckSampleNum(check_status, project);
                check_status = postCheckIpCount(check_status, project, source, ip);
                Pair<Boolean, List<SurveyQuotaCheckItemVo>> resultjson = surveyCommonService.isQuotafull(project_id, seq, total_answer, null);
                boolean quota_check_fail = resultjson.getValue0();
                List<SurveyQuotaCheckItemVo> quota_checkitem_list = resultjson.getValue1();
                log.info("/s/ isQuotafull result val0={},val1={}", quota_check_fail, JSON.toJSONString(quota_checkitem_list));
                if (quota_check_fail) {
                    log.error("/s/ 配额已满");
                    //非测试状态，样本状态为完成时，更新为配额满
                    check_status = status = RESPONDENT_STATUS_BACKUP_VALUE;
                } else {
                    //正式、有效样本 更新配额items
                    if (!ObjectUtils.isEmpty(quota_checkitem_list) && !Objects.equals(filter_status, RESPONDENT_STATUS_DISCARD_VALUE)) {
                        updateQuotaCountV2(respondent, quota_checkitem_list);
                    }
                }

                if (Objects.equals(check_status, RESPONDENT_STATUS_BACKUP_VALUE)) {
                    //update status，然后在getById
                    SurveyRespondent update = new SurveyRespondent();
                    update.setStatus(RESPONDENT_STATUS_BACKUP_VALUE);
                    update.setId(respondent.getId());
                    update.setUpdatedDt(LocalDateTime.now());
                    surveyRespondentService.updateById(update);

                    respondent = surveyRespondentService.getById(respondent.getId());
                }
            }

            if (Objects.equals(filter_status, RESPONDENT_STATUS_DISCARD_VALUE)) {
                respondent = surveyCommonService.discardAutoFilter(respondent);
            }
        } catch (Exception e) {
            log.error("t17 Exception>>>");
        } finally {
            //python 异步了
            surveyCommonService.saveRespondentpropDelay(
                    respondent,
                    project_id,
                    seq,
                    total_answer,
                    deliver_id,
                    deliver_g_id,
                    member_id,
                    custom_answer
            );

            surveyCommonService.mqSurveyRespondentSubmit(respondent, total_answer, custom_answers, null);
        }

        if (is_new && StrUtil.isNotBlank(deliver_key)) {
            redisService.hashSet(deliver_key, deliver_hkey, "0");
        }

        if (is_new && StrUtil.isNotBlank(check_ext_code)) {
            redisService.set(ext_key, "0");
        }

        JSONObject ret_data = new JSONObject();
        ret_data.put("survey_id", project_id);
        ret_data.put("seq", seq);
        ret_data.put("status", status);
        ret_data.put("source", source);
        ret_data.put("survey_ip", ServletUtil.getClientIP(request));
        ret_data.put("ts", DateUtil.currentSeconds());
        if (StrUtil.isNotBlank(deliver_id)) {
            ret_data.put("deliver_id", deliver_id);
        }
        if (StrUtil.isNotBlank(deliver_g_id)) {
            ret_data.put("deliver_group_id", deliver_g_id);
        }
        if (StrUtil.isNotBlank(again_deliver_record_id)) {
            ret_data.put("again_deliver_record_id", again_deliver_record_id);
        }
        if (respondent.getFinishTime() != null && respondent.getBeginTime() != null) {
            ret_data.put("timecost", LocalDateTimeUtil.between(respondent.getBeginTime(), respondent.getFinishTime(), ChronoUnit.SECONDS));
        }
        ret_data.put("sig", SignUtil.genSign(ret_data, surveyCommonService.getSurveySecret()));
        return XmResultJson.success(ret_data);
    }

    private void updateQuotaCountV2(SurveyRespondent respondent, List<SurveyQuotaCheckItemVo> quota_checkitem_list) {
        log.info("/s/ updateQuotaCountV2");
        List<String> quota_checkitem_ids = quota_checkitem_list.stream().map(SurveyQuotaCheckItem::getId).collect(Collectors.toList());
        Set<String> quota_cond_ids = quota_checkitem_list.stream().map(SurveyQuotaCheckItem::getQuotaCondId).collect(Collectors.toSet());

        //一个吊意思
        //select quota_id from  survey_quotacondition  group by quota_id; java
        //select distinct quota_id from survey_quotacondition; python
        List<SurveyQuotaCondition> list = surveyQuotaConditionService.list(Wrappers.<SurveyQuotaCondition>lambdaQuery()
                .select(SurveyQuotaCondition::getQuotaId)
                .in(SurveyQuotaCondition::getId, quota_cond_ids)
                .groupBy(SurveyQuotaCondition::getQuotaId));
        if (!ObjectUtils.isEmpty(list)) {
            list.forEach(it -> {
                log.info("/s/ updateQuotaCountV2 refreshQuotaProgress");
                //刷新配额进度缓存
                surveyQuotaConditionService.refreshQuotaProgress(it.getQuotaId());
            });
        }

        if (!ObjectUtils.isEmpty(quota_checkitem_ids)) {
            List<SurveyRespondentQuotaCheckItem> insertList = new ArrayList<>();
            quota_checkitem_ids.forEach(quota_checkitem_id -> {
                SurveyRespondentQuotaCheckItem item = new SurveyRespondentQuotaCheckItem();
                item.setRid(respondent.getId());
                item.setQuotaCheckitemId(quota_checkitem_id);
                insertList.add(item);
            });
            log.info("/s/ updateQuotaCountV2 saveBatch insertList.size={}", insertList.size());
            surveyRespondentQuotaCheckItemService.saveBatch(insertList);
        }


        //发布新增配额消息
        //params = {
        //        "project_id": respondent.project_id,
        //        "quota_checkitem_ids": quota_checkitem_ids
        //}
        //await redis_mq.publish(topics.survey_add_quote, params)
        CreateQuotaDto mqdata = new CreateQuotaDto();
        mqdata.setProjectId(respondent.getProjectId());
        mqdata.setQuotaCheckitemIds(quota_checkitem_ids);
        surveyMessageSendService.surveyAddQuoteSend(mqdata);
    }

    private Integer postCheckIpCount(Integer check_status, ProjectDto project, Integer source, String ip) {
        log.info("postCheckIpCount check_status={},project-id={},project-customAttr={}", check_status, project.getId(), project.getCustomAttr());

        boolean is_test_mode = Objects.equals(source, RESPONDENT_SOURCE_PREVIEW_VALUE);
        JSONObject customAttr = JSONObject.parseObject(project.getCustomAttr());
        String ipOnce_time = customAttr.getString("ipOnce_time");
        Long ipOnce_num = customAttr.getLong("ipOnce_num");
        boolean ipOnce = customAttr.getBooleanValue("ipOnce");

        String companyId = project.getCompanyId();
        String companyIdKey = companyId.substring(companyId.length() - 4);
        String key = String.format(SURVEY_CNT_PER_IP_FORMAT_KEY, companyIdKey, project.getId());
        Long rspd_ip_count = redisService.hashIncr(key, ip, 1L);

        if (!is_test_mode && ipOnce && (ipOnce_num != null && ipOnce_num > 0) && StrUtil.equals("only", ipOnce_time)) {
            log.info("postCheckIpCount 111111111111");
            if (rspd_ip_count > ipOnce_num) {
                log.info("postCheckIpCount 22222222222222");
                check_status = RESPONDENT_STATUS_BACKUP_VALUE;
                return check_status;
            }
        }
        return check_status;
    }

    private Integer postCheckSampleNum(Integer check_status, ProjectDto project) {
        log.info("postCheckSampleNum check_status={},project-id={},project-customAttr={}", check_status, project.getId(), project.getCustomAttr());
        //问卷正常提交，先增加 对应数量, 超过样本量限制标记为备用
        //问卷提交即计数，问卷答题过程中，开关样本量限制随时生效
        //完成答卷数量增加
        Long rspd_count = redisService.incr(String.format(SURVEY_PROJECT_ID_CNT_RESPONDENT_FORMAT, project.getId()));
        JSONObject attr = JSONObject.parseObject(project.getCustomAttr());
        if (attr.getBooleanValue("nums_flag") && StrUtil.isNotBlank(attr.getString("sample_num"))) {
            if (rspd_count > attr.getInteger("sample_num")) {
                try {
                    //标记本问卷为备用
                    check_status = RESPONDENT_STATUS_BACKUP_VALUE;
                    //达到有效样本数后结束问卷
                    surveyCommonService.finishProject(project.getId());
                    return check_status;
                } catch (Exception e) {
                    //结束问卷出错不反馈到答题端
                    log.error("grpc:finishProject(" + project.getId() + ") failed");
                }
            }
        }
        return check_status;
    }


    private Map<String, Object> checkCommon(String project_id, Integer source, JSONObject param_dict, String authorization) {
        //b接口返回的jwt_token字段会带到这里，需要处理token获取seq
        Map<String, Object> current = checkToken(authorization, project_id);
        source = getSource(source, param_dict);

        JSONObject all_params = new JSONObject();
        String param_sig = getParamSig(source, all_params, param_dict);

        //检查参数签名，签名不同任务投放参数不同，即不同的投放链接
        if (!StrUtil.equals(param_sig, current.get("psig").toString())) {
            throw new BusinessException(SurveyErrorCodeEnum.INVALID_SEQ.getValue(), "invalid psig=" + param_sig);
        }
        return current;
    }

    private String createToken(String pid, Integer version, Integer source, Integer seq, String param_sig, String dev_id) {
        Map<String, Object> kwargs = new HashMap<>();
        kwargs.put("pid", pid);
        kwargs.put("ver", version);
        kwargs.put("src", source);
        kwargs.put("seq", seq);
        kwargs.put("rnd", RandomStringUtils.randomAscii(8));
        kwargs.put("psig", param_sig);
        kwargs.put("did", dev_id);
        return TOKEN_HEADER + jwtTokenService.createToken(null, kwargs, 30 * 24 * 60 * 60);
    }

    private Integer getSource(Integer source, JSONObject params) {
        if (params.containsKey("source")) {
            source = params.getInteger("source");
        }
        if (!Arrays.stream(RespondentSourceEnum.values()).map(RespondentSourceEnum::getValue).collect(Collectors.toList()).contains(source)) {
            log.error("[getSource] source={}", source);
            throw new BusinessException(String.valueOf(ReqErrorCodeEnum.PARAM_FORMAT.getCode()), String.format("source[%s] is invalid", source));
        }
        params.put("source", source.toString());
        return source;
    }

    private Map<String, Object> checkToken(String authorization, String project_id) {
        if (StrUtil.isBlank(authorization)) {
            log.error("[checkToken] 11111");
            throw new BusinessException(String.valueOf(ReqErrorCodeEnum.INVALID_TOKEN.getCode()), "header中authorization不能为空");
        }

        String[] authorization_arry = authorization.split(" ");
        if (authorization_arry.length != 2) {
            log.error("[checkToken] 22222");
            throw new BusinessException(String.valueOf(ReqErrorCodeEnum.INVALID_TOKEN.getCode()), "header中authorization格式不对");
        }

        String token = StrUtil.trimToEmpty(authorization_arry[1]);
        Map<String, Object> tmp = jwtTokenService.getPayloads(token);
        if (tmp.isEmpty()) {
            log.error("[checkToken] 33333");
            throw new BusinessException(String.valueOf(ReqErrorCodeEnum.INVALID_TOKEN.getCode()), "invalid token");
        }

        if (!StrUtil.equals(project_id, tmp.get("pid").toString())) {
            log.error("[checkToken] 44444");
            throw new BusinessException(String.valueOf(ReqErrorCodeEnum.INVALID_TOKEN.getCode()), "invalid token.");
        }

        return tmp;
    }

    private String getParamSig(Integer source, JSONObject all_params, JSONObject param_dict) {
        boolean is_preview = source.equals(RESPONDENT_SOURCE_PREVIEW_VALUE);
        String param_sig;
        if (is_preview) {
            all_params.put("source", source);
            param_sig = "";
        } else {
            all_params = Optional.ofNullable(param_dict.getJSONObject("allParams")).orElse(new JSONObject());
            param_sig = all_params.getOrDefault("sig", "").toString();
            surveyCommonService.checkDeliverParams(all_params);
        }

        if (!is_preview) {
            surveyCommonService.checkDedicatedDeliverDelete(source, all_params);
            surveyCommonService.checkQrDeliverDisable(source, all_params);
        }
        return param_sig;
    }

    private SurveyRespondent checkRespondent(String project_id, Integer seq) {
        JSONObject query = new JSONObject();
        query.put("seq", seq);
        query.put("project_id", project_id);
        SurveyRespondent rspd = surveyRespondentService.getOneRespondent(query);
        if (!ObjectUtils.isEmpty(rspd)) {
            if (rspd.getStatus().equals(RESPONDENT_STATUS_DISCARD.getValue()) || rspd.getStatus().equals(RESPONDENT_STATUS_DELETE.getValue())) {
                log.error("[checkRespondent]");
                throw new BusinessException(SurveyErrorCodeEnum.SURVEY_DELETED.getValue(), "survey_deleted SEQ=" + seq);
            }
        }
        return rspd;
    }

    @ApiOperation(value = "上传答卷附件")
    @PostMapping(value = {"/projects/{project_id:[a-fA-F0-9]{24}}/rspd-seq/{seq:[0-9]*}/attachments/"})
    public XmResultJson<?> attachment(@PathVariable(value = "project_id") String projectId,
                                      @RequestParam("attachment") List<MultipartFile> files,
                                      @RequestParam Integer qid,
                                      @RequestParam(value = "option_id", defaultValue = "0") Integer optionId,
                                      @RequestParam(value = "keep_ext", defaultValue = "false") Boolean keepExt,
                                      HttpServletRequest request, @PathVariable(value = "seq") Integer seq) {
        String authorization = request.getHeader("authorization");
        Map<String, Object> current = checkToken(authorization, projectId);
        Integer currentSeq = (Integer) current.get("seq");
        if (Objects.isNull(currentSeq)) {
            return XmResultJson.fail(-1, "invalid token");
        }
        if (!Objects.equals(seq, currentSeq)) {
            return XmResultJson.fail(-1, "seq不一致");
        }
        return XmResultJson.success(surveyRespondentAttachmentService.attachment(projectId, currentSeq, files, qid, optionId, keepExt));
    }

    // /api/survey/projects/65003c003e2c0c0009ae6dd7/rspd-seq/100237/attachments/?qid=2&source=2&_=1694515525072
    @ApiOperation(value = "获取某个答卷的附件")
    @GetMapping(value = {"/projects/{project_id:[a-fA-F0-9]{24}}/rspd-seq/{seq:[0-9]*}/attachments/"})
    public XmResultJson<?> getAttachments(@PathVariable(value = "project_id") String projectId,
                                          @PathVariable(value = "seq") Integer seq,
                                          @RequestParam Integer qid,
                                          @RequestParam(required = false) Integer option_id,
                                          HttpServletRequest request) {
        String authorization = request.getHeader("authorization");
        Map<String, Object> current = checkToken(authorization, projectId);
        Integer currentSeq = (Integer) current.get("seq");
        if (Objects.isNull(currentSeq)) {
            return XmResultJson.fail(-1, "invalid token");
        }
        if (!Objects.equals(seq, currentSeq)) {
            return XmResultJson.fail(-1, "seq不一致");
        }
        return XmResultJson.success(surveyRespondentAttachmentService.getAttachments(projectId, currentSeq, qid, option_id));
    }

    @ApiOperation(value = "删除答卷附件")
    @DeleteMapping(value = {"/projects/{project_id:[a-fA-F0-9]{24}}/rspd-seq/{seq:[0-9]*}/attachments/{att_id:[a-fA-F0-9]{24}}"})
    public XmResultJson<?> deleteAttachment(@PathVariable(value = "project_id") String projectId,
                                            @PathVariable(value = "att_id") String attId,
                                            HttpServletRequest request, @PathVariable Integer seq) {
        String authorization = request.getHeader("authorization");
        Map<String, Object> current = checkToken(authorization, projectId);
        Integer currentSeq = (Integer) current.get("seq");
        if (Objects.isNull(currentSeq)) {
            return XmResultJson.fail(-1, "invalid token");
        }
        if (!Objects.equals(seq, currentSeq)) {
            return XmResultJson.fail(-1, "seq不一致");
        }
        JSONObject obj = new JSONObject();
        obj.put("deletedCount", surveyRespondentAttachmentService.deleteAttachment(projectId, currentSeq, attId));
        return XmResultJson.success(obj);
    }

    // /api/survey/projects/64fad65e3e2c0c0008ae6bf5/submited-options/?gid=2&seq_list=3,4,&source=2&_=1694160530203
    @ApiOperation(value = "答题后展示投票结果")
    @GetMapping("/projects/{id:[a-fA-F0-9]{24}}/submited-options/")
    public Object getSubmitedOptions(@PathVariable(value = "id") String pid,
                                     @RequestParam(value = "seq_list") String seqListStr,
                                     @RequestParam(name = "gid") Integer gid, HttpServletRequest request) {

        if (!ReUtil.isMatch("^\\d[,\\d]*", seqListStr)) {
            throw new BusinessException(String.valueOf(ReqErrorCodeEnum.PARAM_FORMAT.getCode()), "seq_list不合法");
        }
        String authorization = request.getHeader("authorization");
        Map<String, Object> current = checkToken(authorization, pid);
        Integer currentSeq = (Integer) current.get("seq");
        if (Objects.isNull(currentSeq)) {
            return XmResultJson.fail(-1, "invalid token");
        }

        seqListStr = StrUtil.removePrefix(seqListStr, ",");
        seqListStr = StrUtil.removeSuffix(seqListStr, ",");
        List<Integer> seq_list = Arrays.stream(seqListStr.split(",")).map(Integer::valueOf).collect(Collectors.toList());
        return surveyRespondentPropDataService.getSubmitedCount(pid, gid, seq_list, Boolean.TRUE);
    }
}